#ifndef __surroundplugin__
#include "surroundplugin.h"
#endif

#ifndef __sdeditor__
#include "editor.h"
#endif

#include <string.h>
#include <stdio.h>
#include <math.h>

//-------------------------------------------------------------------------------------------------------
AudioEffect* createEffectInstance (audioMasterCallback audioMaster)
{
	return new SurroundPlugin (audioMaster);
}

//-----------------------------------------------------------------------------
SurroundPlugin::SurroundPlugin (audioMasterCallback audioMaster)
: Plugin (audioMaster)
, plugInput (0)
, plugOutput (0)
{
	
	setNumInputs (MAX_CHANNELS);
	setNumOutputs (MAX_CHANNELS);

	// We initialize the arrangements to default values.
	// Nevertheless, the host should modify them via
	// appropriate calls to setSpeakerArrangement.
	allocateArrangement (&plugInput, MAX_CHANNELS);
	plugInput->type = kSpeakerArr51;

	allocateArrangement (&plugOutput, MAX_CHANNELS);
	plugOutput->type = kSpeakerArr51;

	setUniqueID ('NM02');	// this should be unique, use the Steinberg web page for plugin Id registration

	// create the editor
	editor = new SDEditor (this);

	resume ();
}

//-----------------------------------------------------------------------------
SurroundPlugin::~SurroundPlugin ()
{
	deallocateArrangement (&plugInput);
	deallocateArrangement (&plugOutput);
}

//------------------------------------------------------------------------
void SurroundPlugin::resume ()
{
	// Here erase all the internal buffers
}

//------------------------------------------------------------------------
bool SurroundPlugin::getSpeakerArrangement (VstSpeakerArrangement** pluginInput, VstSpeakerArrangement** pluginOutput)
{
	*pluginInput  = plugInput;
	*pluginOutput = plugOutput;
	return true;
}

//------------------------------------------------------------------------
bool SurroundPlugin::setSpeakerArrangement (VstSpeakerArrangement* pluginInput,
											VstSpeakerArrangement* pluginOutput)
{
	if (!pluginOutput || !pluginInput)
		return false;

	bool result = true;
	
	// This plug-in can act on any speaker arrangement,
	// provided that there are the same number of inputs/outputs.
	if (pluginInput->numChannels > MAX_CHANNELS)
	{
		// This plug-in can't have so many channels. So we answer
		// false, and we set the input arrangement with the maximum
		// number of channels possible
		result = false;
		allocateArrangement (&plugInput, MAX_CHANNELS);
		plugInput->type = kSpeakerArr51;
	}
	else
	{
		matchArrangement (&plugInput, pluginInput);
	}
	
	if (pluginOutput->numChannels != plugInput->numChannels)
	{
		// This plug-in only deals with symetric IO configurations...
		result = false;
		matchArrangement (&plugOutput, plugInput);
	}
	else
	{
		matchArrangement (&plugOutput, pluginOutput);
	}

	return result;
}

//-----------------------------------------------------------------------------
void SurroundPlugin::setParameter (VstInt32 index, float value)
{
	Plugin::setParameter (index, value);

	if (editor)
		((AEffGUIEditor*)editor)->setParameter (index, value);
}

// Convert a value in dB's to a linear amplitude
float dB_to_Amp(float g)
{
	float result;

	if (g > -90.0) 
		result = (float)pow(10, g * 0.05);
	else 
		result = 0;

	return result;
}

//float abs(float value)
//{
//	if (value < 0) return -value; else return value;
//}

float f_clip(float x, float l, float h)
{
	float x1, x2;

	x1 = abs(x - l);
	x2 = abs(x - h);
	x = x1 + l + h;
	x = x - x2;
	x = (float)(x * 0.5);
	return x;
}

float linear_interpolation(float f, float a, float b)
{
	float result = (1 - f) * a + f * b; 
	return result;
}

float cubic_hermite_interpolation(float t, float p0, float m0, float p1, float m1)
{
	float result = (2 * t * t * t - 3 * t * t + 1) * p0 +
				   (t * t * t - 2 * t * t + t) * m0 +
				   (-2 * t * t * t + 3 * t * t) * p1 +
				   (t * t * t - t * t) * m1;
	return result;
}

//---------------------------------------------------------------------------
// Here goes all the processing
//---------------------------------------------------------------------------
void SurroundPlugin::processReplacing (float** inputs, float** outputs, VstInt32 sampleframes)
{
	int I, J, Chn, M, PreviousM, CROSSAREASIZE, PreviousMaxID, MaxID, LSubBegin, LSubEnd, RSubBegin, RSubEnd, LSubMaxID, RSubMaxID; //iterators and markers
    float CurrentSample, RealGain, Gain, CrossingGain, NewGain, CompThresh, PreviousGain, CurrentGain, LSubRealGain, RSubRealGain;
    bool Sign, CurrentSign;     //signs (+, -)
    float MaxVal, LSubPeakVal, RSubPeakVal, t, SliceThreshold;
    float *CrossArea;
	float SLICENUM = 4;

	// Init MaxID
	MaxID = 0; LSubMaxID = 0; RSubMaxID = 0;
	
	// Grab the gain setting from the form / editor
	GainDB = (int)((fOut * 24) + 0.5f);
	
	for (Chn = 0; Chn < plugInput->numChannels; Chn++)
	{
		float* input  = inputs[Chn];
		float* output = outputs[Chn];
		
		//initialization
		Gain = dB_to_Amp((float)GainDB);

		//find the first zero-crossing (crossing area size)
		if (input[0] >= 0) Sign = true;	else Sign = false;
		J = 0; CurrentSign = Sign;

		while ((Sign == CurrentSign) && (J < sampleframes)) 
		{
			J++;
			if (input[J] >= 0) CurrentSign = true; else CurrentSign = false;
		}

		CROSSAREASIZE = J;

		//------ MAIN AREA--------//
		M = 0; RealGain = 1.0; PreviousMaxID = 0;
		//process main part (from the end of crossing area)
		for (I = CROSSAREASIZE; I < sampleframes; I++) 
		{
			if (I >= M) 
			{
				// backward interpolation
				if ((PreviousMaxID > 0) && (RealGain < PreviousGain)) 
				{
					for (J = PreviousM; J > PreviousMaxID; J--) 
					{
						t = f_clip(float(J-PreviousMaxID+1)/float(PreviousM-PreviousMaxID+1), 0.0, 1.0);
						CurrentGain = cubic_hermite_interpolation(t, PreviousGain, 0, RealGain, 0);
						CurrentSample = output[J] * CurrentGain / PreviousGain;
						output[J] = CurrentSample;
					}
				}
	     
				// Store previous real gain, max. ID and Marker ID
				PreviousGain = RealGain;
				PreviousMaxID = MaxID;
				PreviousM = M;

				//find the next zero-crossing (marker)
				if (input[I] >= 0) Sign = true; else Sign = false;
				J = I; CurrentSign = Sign;
				while ((Sign == CurrentSign) && (J < sampleframes)) 
				{
					J++;
					if (input[J] >= 0) CurrentSign = true; else CurrentSign = false;
				}
				M = J;

				// Obtain max. amplitude of new region (and its position)
				MaxVal = 0.0;
				for (J = I; J <= M; J++)
				{
					if (abs(input[J]) > abs(MaxVal)) 
					{
						MaxVal = input[J];
						MaxID = J;
					}
				}
				MaxVal = f_clip(MaxVal, -1.0, 1.0);
				
				LSubMaxID = 0;
				
				int SliceDirection = 1;

				for (int d = 0; d < 2; d++) 
				{
					// Obtain max. amplitude of left subregion (and its position)
					LSubPeakVal = 0;
					SliceThreshold = (MaxVal / 2);
					LSubBegin = I;
					LSubEnd = I;

					while ((LSubMaxID == 0) && (abs(SliceThreshold) < abs(MaxVal)))
					{
						// Init subregion boundaries
						LSubBegin = I;
						LSubEnd = I;

						// Slice the left half of region
						for (J = I; J < MaxID; J++)
						{
							// Find the first sample above slice threshold
							if ((abs(input[J]) > abs(SliceThreshold)) && (LSubBegin == I))
							{
								LSubBegin = J;
							}
							
							// Find the last sample under slice threshold
							if (abs(input[J]) < abs(SliceThreshold))
							{
								LSubEnd = J;
							}
						}
						
						// If length of subregion > 0 and if there's a "hole" between SubEnd and MaxID at least 1 sample
						if ((LSubEnd > LSubBegin) && (LSubEnd < (MaxID-1)))
						{
							// new subregion was found -> find the ID and value of max. sample in subregion
							LSubPeakVal = 0.0;
							for (J = I; J < LSubEnd; J++)
							{
								if (abs(input[J]) > abs(LSubPeakVal)) 
								{
									LSubPeakVal = input[J];
									LSubMaxID = J;
								}
							}
						}

						// Update the slice threshold
						SliceThreshold += ((SliceDirection * MaxVal) / (2 * SLICENUM));
					}
					
					LSubPeakVal = f_clip(LSubPeakVal, -1.0, 1.0);

					if (LSubMaxID != 0)
						break;

					SliceDirection *= -1;
				}
				
				RSubMaxID = 0; 

				SliceDirection = 1;

				for (int d = 0; d < 2; d++) 
				{
					// Obtain max. amplitude of right subregion (and its position)
					RSubPeakVal = 0.0;
					SliceThreshold = (MaxVal / 2);
					RSubBegin = M;
					RSubEnd = M;
					while ((RSubMaxID == 0) && (abs(SliceThreshold) < abs(MaxVal)))
					{
						// Init subregion boundaries
						RSubBegin = MaxID;
						RSubEnd = MaxID;

						// Slice the right half of region
						for (J = MaxID; J < M; J++)
						{
							// Find the first sample under slice threshold
							if ((abs(input[J]) < abs(SliceThreshold)) && (RSubBegin == MaxID))
							{
								RSubBegin = J;
							}
							
							// Find the last sample above slice threshold
							if (abs(input[J]) > abs(SliceThreshold))
							{
								RSubEnd = J;
							}
						}
						
						// If length of subregion > 0 and if there's a "hole" between MaxID and SubBegin at least 1 sample
						if ((RSubEnd > RSubBegin) && (RSubBegin > (MaxID+1)))
						{
							// new subregion was found -> find the ID and value of max. sample in subregion
							RSubPeakVal = 0.0;
							for (J = RSubBegin; J < M; J++)
							{
								if (abs(input[J]) > abs(RSubPeakVal)) 
								{
									RSubPeakVal = input[J];
									RSubMaxID = J;
								}
							}
						}

						// Update the slice threshold
						SliceThreshold += ((SliceDirection * MaxVal) / (2 * SLICENUM));
					}
					
					RSubPeakVal = f_clip(RSubPeakVal, -1.0, 1.0);

					if (RSubMaxID != 0)
						break;

					SliceDirection *= -1;
				}

				// Adjust real gain according to the max. amplitude
				if (abs(MaxVal * Gain) < 1.0) 
					RealGain = Gain;
				else 
					RealGain = (float)abs(1.0 / MaxVal);

				// Compute real gain for left subregion
				if (abs(LSubPeakVal * Gain) < 1.0) 
					LSubRealGain = Gain;
				else
				{
					if (LSubPeakVal != 0)
						LSubRealGain = (float)abs(1.0 / LSubPeakVal);
				}

				// Compute real gain for right subregion
				if (abs(RSubPeakVal * Gain) < 1.0) 
					RSubRealGain = Gain;
				else 
				{
					if (RSubPeakVal != 0)
						RSubRealGain = (float)abs(1.0 / RSubPeakVal);
				}

			}
			
			// LEFT SUBREGION: [LSubBegin to LSubEnd]
			// LSubMaxID - Position of left region peak
			// LSubPeakVal - Value of left region peak
			// (if left subregion not present, LSubMaxID = 0)

			// RIGHT SUBREGION: [RSubBegin to RSubEnd]
			// RSubMaxID - Position of left region peak
			// RSubPeakVal - Value of left region peak
			// (if right subregion not present, RSubMaxID = 0)

			if (RealGain < PreviousGain)
			{
				// Current gain cannot be interpolated otherwise clipping can occur
				CurrentGain = RealGain;
			}
			else
			{
				// CurrentGain will be computed by linear interpolation
				// between PreviousGain and RealGain
				t = f_clip(float(I-PreviousM+1)/float(MaxID-PreviousM+1), 0.0, 1.0);
				CurrentGain = cubic_hermite_interpolation(t, PreviousGain, 0, RealGain, 0);
			}

			// Perform additional maximization of left subregion
			if ((LSubRealGain > CurrentGain) && (LSubMaxID > 0) && (I < LSubEnd) ) //&& (I > LSubBegin)
			{
				if (I < LSubMaxID)
					t = f_clip(float(I-LSubBegin)/float(LSubMaxID-LSubBegin+1), 0.0, 1.0);
				else
					t = 1.0f - f_clip(float(I-LSubMaxID)/float(LSubEnd-LSubMaxID+1), 0.0, 1.0);

				CurrentGain = cubic_hermite_interpolation(t, CurrentGain, 0, LSubRealGain, 0);
				
				//CurrentGain = LSubRealGain;
			}

			// Perform additional maximization of right subregion
			if ((RSubRealGain > CurrentGain) && (RSubMaxID > 0) && (I > RSubBegin) ) //&& (I < RSubEnd)
			{
				if (I < RSubMaxID)
					t = f_clip(float(I-RSubBegin)/float(RSubMaxID-RSubBegin+1), 0.0, 1.0);
				else
					t = 1.0f - f_clip(float(I-RSubMaxID)/float(RSubEnd-RSubMaxID+1), 0.0, 1.0);

				CurrentGain = cubic_hermite_interpolation(t, CurrentGain, 0, RSubRealGain, 0);

				//CurrentGain = RSubRealGain;
			}

			// Temp.:
			// if ((I == LSubMaxID) || (I == RSubMaxID)) CurrentGain = 0.0;

			// Maximize current sample
			CurrentSample = f_clip(input[I], -1.0, 1.0) * CurrentGain;

		    // send data to output
			output[I] = CurrentSample;

		}

		//------ CROSSING AREA--------//
		// Obtain max. amplitude of crossing area
		MaxVal = 0.0;
		for (J = 0; J < CROSSAREASIZE; J++)
		{
			if (abs(input[J]) > MaxVal) 
				MaxVal = abs(input[J]);
		}
		MaxVal = f_clip(MaxVal, 0.0, 1.0);

		// Adjust real crossing gain according to the max. amplitude
		if ((MaxVal * Gain) < 1.0) 
			NewGain = Gain;
		else 
			NewGain = (float)(1.0 / MaxVal);

		//process crossing area
		CrossArea = new float[CROSSAREASIZE];
		
		// Calculate crossing gain - linear interpolation between last and new gain
		for (I = 0; I < CROSSAREASIZE; I++)
		{
			CrossingGain = cubic_hermite_interpolation(I / (float)CROSSAREASIZE, LastGain[Chn], 0, NewGain, 0);
			// Apply crossing gain to crossing area
			CurrentSample = f_clip(input[I], -1.0, 1.0) * CrossingGain;
			// Save crossing area into the buffer
			CrossArea[I] = CurrentSample;
		}

		// Obtain !NON-ABSOLUTE! max. amplitude of maximized crossing area
		MaxVal = 0.0;
		for (I = 0; I < CROSSAREASIZE; I++)
		{
			if (abs(CrossArea[I]) > abs(MaxVal)) 
				MaxVal = CrossArea[I];
		}

		// Check positive distortion (clips over 1.0)
		if (MaxVal > 1.0) 
		{
			// if the current sample is greater than the first sample, ...
			for (I = 0; I < CROSSAREASIZE; I++)
			{
				// Interpolate compressor threshold between last sample and zero
				CompThresh = cubic_hermite_interpolation(I / (float)CROSSAREASIZE , LastSample[Chn], 0, 0.0, 0);

				if (CrossArea[I] > CompThresh) 
				{
					// apply dynamic compression
					CrossArea[I] = CompThresh +
								  (CrossArea[I]-CompThresh) *
								  float(1.0 - CompThresh) /
								  (MaxVal - CompThresh);
				}
			}
		}
	        
		// Check negative distortion (clips under -1.0)
		if (MaxVal < -1.0) 
		{
			// if the current sample is greater than the first sample, ...
			for (I = 0; I < CROSSAREASIZE; I++)
			{
				// Interpolate compressor threshold between last sample and zero
				CompThresh = cubic_hermite_interpolation(I / (float)CROSSAREASIZE ,LastSample[Chn], 0, 0.0, 0);

				if (CrossArea[I] < CompThresh) 
				{
					// apply dynamic compression
					CrossArea[I] = CompThresh +
								   (CrossArea[I]-CompThresh) *
								   float(1.0 + CompThresh) /
								   (CompThresh - MaxVal);
				}
			}
		}
	       
		// send data to output
		for (I = 0; I < CROSSAREASIZE; I++)
		{	
			 output[I] = CrossArea[I];
		}
	  
		// Store the last real gain
		LastGain[Chn] = RealGain;

		// Store the last sample
		LastSample[Chn] = output[sampleframes-1];
	}

}

